/*
 * Copyright 2013 Sigurd Randoll.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package de.digiway.rapidbreeze.client.controller.downloadtable;

import de.digiway.rapidbreeze.client.config.ClientConfiguration;
import de.digiway.rapidbreeze.client.infrastructure.BusEvents;
import de.digiway.rapidbreeze.client.infrastructure.ThreadPoolService;
import de.digiway.rapidbreeze.client.model.download.DownloadModel;
import de.digiway.rapidbreeze.client.model.download.DownloadModelList;
import de.digiway.rapidbreeze.client.model.download.DownloadResourceRepository;
import de.digiway.rapidbreeze.shared.rest.download.DownloadResource;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.ResourceBundle;
import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.ProgressBar;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.ProgressBarTableCell;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.util.Callback;
import org.rribbit.Listener;

/**
 *
 * @author Sigurd
 */
public class DownloadTableController implements Initializable {

    @FXML
    private TableView<DownloadModel> downloadTable;
    @FXML
    private TableColumn<DownloadModel, String> cloudProviderColumn;
    @FXML
    private TableColumn<DownloadModel, Long> etaColumn;
    @FXML
    private TableColumn<DownloadModel, String> filenameColumn;
    @FXML
    private TableColumn<DownloadModel, Double> progressColumn;
    @FXML
    private TableColumn<DownloadModel, String> statusColumn;
    private DownloadResourceRepository repository;
    private ClientConfiguration clientConfiguration;
    private DownloadModelList modelList;
    private ScheduledExecutorService scheduledExecutorService;
    private Executor queuedExecutor;
    private Boolean connected;
    private static final Logger LOG = Logger.getLogger(DownloadTableController.class.getName());

    @Override
    public void initialize(URL url, ResourceBundle rb) {
        this.filenameColumn.prefWidthProperty().bind(downloadTable.widthProperty().multiply(2f / 6f).subtract(5));
        this.cloudProviderColumn.prefWidthProperty().bind(downloadTable.widthProperty().multiply(1f / 6f));
        this.progressColumn.prefWidthProperty().bind(downloadTable.widthProperty().multiply(1f / 6f));
        this.etaColumn.prefWidthProperty().bind(downloadTable.widthProperty().multiply(1f / 6f));
        this.statusColumn.prefWidthProperty().bind(downloadTable.widthProperty().multiply(1f / 6f));

        this.scheduledExecutorService = ThreadPoolService.getScheduledPool();
        this.queuedExecutor = ThreadPoolService.getQueuedPool();
        this.modelList = new DownloadModelList();

        this.cloudProviderColumn.setCellValueFactory(new PropertyValueFactory<DownloadModel, String>(DownloadModel.PROP_CLOUD_PROVIDER));
        this.etaColumn.setCellValueFactory(new PropertyValueFactory<DownloadModel, Long>(DownloadModel.PROP_ETA));
        this.filenameColumn.setCellValueFactory(new PropertyValueFactory<DownloadModel, String>(DownloadModel.PROP_FILENAME));
        this.statusColumn.setCellValueFactory(new PropertyValueFactory<DownloadModel, String>(DownloadModel.PROP_TRANSLATED_STATUS));
        this.progressColumn.setCellValueFactory(new PropertyValueFactory<DownloadModel, Double>(DownloadModel.PROP_PROGRESS));
        this.progressColumn.setCellFactory(new Callback<TableColumn<DownloadModel, Double>, TableCell<DownloadModel, Double>>() {
            @Override
            public TableCell<DownloadModel, Double> call(TableColumn<DownloadModel, Double> p) {
                return new ProgressCell();
            }
        });

        this.downloadTable.setContextMenu(new DownloadTableContextMenu(downloadTable));
        this.downloadTable.setDisable(true);
        this.downloadTable.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
        this.downloadTable.setItems(modelList);
    }

    /**
     * Nearly identical to the original ProgressBarTableCell but with a full
     * width progress bar.
     */
    private static class ProgressCell extends ProgressBarTableCell<DownloadModel> {

        @Override
        public void updateItem(Double progress, boolean empty) {
            super.updateItem(progress, empty);
            ProgressBar progressBar = (ProgressBar) getGraphic();
            if (progress != null) {
                if (!progressBar.prefWidthProperty().isBound()) {
                    progressBar.prefWidthProperty().bind(getTableColumn().widthProperty());
                }
            }
        }
    }

    public void setDownloadResourceRepository(DownloadResourceRepository repository) {
        this.repository = repository;
    }

    public void setClientConfiguration(ClientConfiguration clientConfiguration) {
        this.clientConfiguration = clientConfiguration;
    }

    @Listener(hint = BusEvents.SERVER_CONNECTION_CHANGED_EVENT)
    public void onConnectionChanged(boolean newState) {
        if (connected == null) {
            // Late initialization:
            scheduledExecutorService.scheduleAtFixedRate(new UpdateTask(), 0, clientConfiguration.getServerPollingInterval(), TimeUnit.MILLISECONDS);
        }
        connected = newState;
        downloadTable.setDisable(!connected);
    }

    @Listener(hint = BusEvents.CLEAR_FINISHED_DOWNLOADS)
    public void onClearFinished() {
        queuedExecutor.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    for (DownloadResource resource : repository.getDownloads()) {
                        if (resource.isFinished()) {
                            repository.removeDownload(resource);
                        }
                    }
                } catch (RuntimeException ex) {
                    LOG.log(Level.SEVERE, "Exception occured during resource removal.", ex);
                }
            }
        });
    }

    @Listener(hint = BusEvents.MENU_ITEM_DELETE_ALL_EVENT)
    public void onDeleteAll() {
        queuedExecutor.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    List<DownloadResource> downloads = new ArrayList<>(repository.getDownloads());
                    // Resort downloads to have running downloads at the end. This speeds up deletion, 
                    // because the autopilot tries to start a new download for each deleted download. 
                    // This causes too much starting and stopping downloads for nothing.
                    Collections.sort(downloads, new Comparator<DownloadResource>() {
                        @Override
                        public int compare(DownloadResource o1, DownloadResource o2) {
                            if (o1.isRunning()) {
                                return 1;
                            }
                            return -1;
                        }
                    });
                    for (DownloadResource resource : downloads) {
                        repository.removeDownload(resource);
                    }
                } catch (RuntimeException ex) {
                    LOG.log(Level.SEVERE, "Exception occured during resource removal.", ex);
                }
            }
        });
    }

    @Listener(hint = BusEvents.MENU_ITEM_DELETE_EVENT)
    public void onDeleteDownload(final List<DownloadModel> list) {
        queuedExecutor.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    // Reorder downloads, see above.
                    List<DownloadModel> downloads = new ArrayList<>(list);
                    Collections.sort(downloads, new Comparator<DownloadModel>() {
                        @Override
                        public int compare(DownloadModel o1, DownloadModel o2) {
                            if (o1.isRunning()) {
                                return 1;
                            }
                            return -1;
                        }
                    });
                    for (DownloadModel model : downloads) {
                        DownloadResource download = repository.getDownload(model.getIdentifier());
                        repository.removeDownload(download);
                    }
                } catch (RuntimeException ex) {
                    LOG.log(Level.SEVERE, "Exception occured during resource removal.", ex);
                }
            }
        });
    }

    @Listener(hint = BusEvents.MENU_ITEM_PAUSE_EVENT)
    public void onPauseDownload(final List<DownloadModel> downloads) {
        queuedExecutor.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    for (DownloadModel model : downloads) {
                        DownloadResource download = repository.getDownload(model.getIdentifier());
                        download.pause();
                        repository.updateDownload(download);
                    }
                } catch (RuntimeException ex) {
                    LOG.log(Level.SEVERE, "Exception occured during resource update.", ex);
                }
            }
        });
    }

    @Listener(hint = BusEvents.MENU_ITEM_UNPAUSE_EVENT)
    public void onUnpauseDownload(final List<DownloadModel> downloads) {
        queuedExecutor.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    for (DownloadModel model : downloads) {
                        DownloadResource download = repository.getDownload(model.getIdentifier());
                        download.waitState();
                        repository.updateDownload(download);
                    }
                } catch (RuntimeException ex) {
                    LOG.log(Level.SEVERE, "Exception occured during resource update.", ex);
                }
            }
        });
    }

    @Listener(hint = BusEvents.MENU_ITEM_START_EVENT)
    public void onStartDownload(final List<DownloadModel> downloads) {
        queuedExecutor.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    for (DownloadModel model : downloads) {
                        DownloadResource download = repository.getDownload(model.getIdentifier());
                        download.start();
                        repository.updateDownload(download);
                    }
                } catch (RuntimeException ex) {
                    LOG.log(Level.SEVERE, "Exception occured during resource update.", ex);
                }
            }
        });
    }

    private class UpdateTask implements Runnable {

        @Override
        public void run() {
            try {
                if (connected != null && connected) {
                    modelList.update(repository.getDownloads());
                }

            } catch (RuntimeException ex) {
                LOG.log(Level.SEVERE, "Error occured during update of " + DownloadModelList.class.getSimpleName() + ".", ex);
            }
        }
    }
}
